package com.yiduo.outpost.core.redis.config;

import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.caffeine.CaffeineCacheManager;
import org.springframework.data.redis.cache.RedisCacheManager;

import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.Callable;

/**
 * 多级缓存管理器
 */
public class MultiLevelCacheManager implements CacheManager {

	private final CaffeineCacheManager caffeineCacheManager;
	private final RedisCacheManager redisCacheManager;

	public MultiLevelCacheManager(CaffeineCacheManager caffeineCacheManager, RedisCacheManager redisCacheManager) {
		this.caffeineCacheManager = caffeineCacheManager;
		this.redisCacheManager = redisCacheManager;
	}

	@Override
	public Cache getCache(String name) {
		Cache caffeineCache = caffeineCacheManager.getCache(name);
		Cache redisCache = redisCacheManager.getCache(name);
		return new MultiLevelCache(caffeineCache, redisCache);
	}

	@Override
	public Collection<String> getCacheNames() {
		Set<String> names = new HashSet<>();
		names.addAll(caffeineCacheManager.getCacheNames());
		names.addAll(redisCacheManager.getCacheNames());
		return names;
	}

	private static class MultiLevelCache implements Cache {

		private final Cache caffeineCache;
		private final Cache redisCache;

		public MultiLevelCache(Cache caffeineCache, Cache redisCache) {
			this.caffeineCache = caffeineCache;
			this.redisCache = redisCache;
		}

		@Override
		public String getName() {
			return caffeineCache.getName();
		}

		@Override
		public Object getNativeCache() {
			return caffeineCache.getNativeCache();
		}

		@Override
		public ValueWrapper get(Object key) {
			ValueWrapper valueWrapper = caffeineCache.get(key);
			if (valueWrapper != null) {
				return valueWrapper;
			}
			valueWrapper = redisCache.get(key);
			if (valueWrapper != null) {
				caffeineCache.put(key, valueWrapper.get());
			}
			return valueWrapper;
		}

		@Override
		public <T> T get(Object key, Class<T> type) {
			T value = caffeineCache.get(key, type);
			if (value != null) {
				return value;
			}
			value = redisCache.get(key, type);
			if (value != null) {
				caffeineCache.put(key, value);
			}
			return value;
		}

		@Override
		public <T> T get(Object key, Callable<T> valueLoader) {
			T value = caffeineCache.get(key, valueLoader);
			if (value != null) {
				return value;
			}
			value = redisCache.get(key, valueLoader);
			if (value != null) {
				caffeineCache.put(key, value);
			}
			return value;
		}

		@Override
		public void put(Object key, Object value) {
			caffeineCache.put(key, value);
			redisCache.put(key, value);
		}

		@Override
		public ValueWrapper putIfAbsent(Object key, Object value) {
			ValueWrapper valueWrapper = caffeineCache.putIfAbsent(key, value);
			redisCache.putIfAbsent(key, value);
			return valueWrapper;
		}

		@Override
		public void evict(Object key) {
			caffeineCache.evict(key);
			redisCache.evict(key);
		}

		@Override
		public void clear() {
			caffeineCache.clear();
			redisCache.clear();
		}
	}
}